package cn.seaboot.admin.upload;

import cn.seaboot.commons.core.Asserts;
import cn.seaboot.commons.core.UUIDUtils;
import cn.seaboot.commons.digest.MD5;
import cn.seaboot.commons.file.FilenameUtils;
import cn.seaboot.commons.file.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;

/**
 * 文件上传工具，用法如下：
 * <p>
 * Uploader.storage("image").write(new File(""));
 * <p>
 * 算法与 FastDFS 类似，唯一参数就是文件/输入流，
 * 最终返回一段路径，路径的结构是：group/hash/guid.extension
 * <p>
 * 程序员不需要关注文件如何存储，文件上传之后，会返回一段 uri，根据 uri 就能获取文件。
 *
 * @author Mr.css
 * @version 2020-12-04 11:19
 */
public class Uploader {

    /**
     * 数据存储
     */
    private static final Map<String, Uploader> storageMap = new HashMap<>();

    /**
     * 注册一个存储
     *
     * @param group   组别
     * @param storage 存储
     */
    public static void createStorage(String group, Storage storage) {
        Uploader uploader = new Uploader(storage);
        storageMap.put(group, uploader);
    }

    /**
     * 引用存储
     *
     * @param group 组别
     * @return 上传组件
     */
    public static Uploader storage(String group) {
        Uploader uploader = storageMap.get(group);
        Asserts.notNull(uploader, "uploader is undefined: {}", group);
        return uploader;
    }

    // static end -----------------------------------------------------------------------------------

    /**
     * private constructor
     */
    public Uploader(Storage storage) {
        this.storage = storage;
    }

    /**
     * 文件存储
     */
    private final Storage storage;

    public Storage getStorage() {
        return storage;
    }

    /**
     * 存储一份文件
     *
     * @param multipartFile MultipartFile
     * @return 相对路径
     * @throws IOException write failed：file path not found or others exceptions
     */
    public OutputInfo write(MultipartFile multipartFile) throws IOException {
        String suffix = FilenameUtils.getExtension(multipartFile.getOriginalFilename());
        try (InputStream inputStream = multipartFile.getInputStream()) {
            return write(inputStream, suffix);
        }
    }

    /**
     * 存储一份文件
     *
     * @param file MultipartFile
     * @return 相对路径
     * @throws IOException write failed：file path not found or others exceptions
     */
    public OutputInfo write(File file) throws IOException {
        String suffix = FilenameUtils.getExtension(file.getName());
        try (InputStream inputStream = IOUtils.openFileInputStream(file)) {
            return write(inputStream, suffix);
        }
    }


    /**
     * 存储一份文件
     *
     * @param inputStream   输入
     * @param suffix        扩展名
     * @return              文件输出信息
     * @throws IOException  -
     */
    private OutputInfo write(InputStream inputStream, String suffix) throws IOException {
        LocalDate date = LocalDate.now();
        String name = UUIDUtils.genUUID32() + "." + suffix;
        String path = date.getYear()
                + File.separator + date.getMonthValue()
                + File.separator + date.getDayOfMonth()
                + File.separator + name;

        this.storage.write(inputStream, path);

        OutputInfo info = new OutputInfo();
        info.setPath(path);
        info.setName(name);

        try(InputStream is = this.openInputStream(path)){
            info.setTag(MD5.hexBit32(is));
        }
        return info;
    }

    /**
     * 删除文件
     *
     * @param relativePath 相对路径
     * @return this
     * @throws IOException file not exists or delete failed
     */
    public Uploader delete(String relativePath) throws IOException {
        storage.delete(relativePath);
        return this;
    }

    /**
     * 覆盖一份文件
     *
     * @param multipartFile multipartFile
     * @param path          if old-path not empty, will overwrite file content, if not empty, will create new file and write data
     * @return old-path, if argument old-path is empty, will create a new one.
     * @throws IOException write failed：file path not found or others exceptions
     */
    public String overwrite(MultipartFile multipartFile, String path) throws IOException {
        try (InputStream inputStream = multipartFile.getInputStream()) {
            storage.overwrite(inputStream, path);
        }
        return path;
    }

    /**
     * 覆盖一份文件
     *
     * @param file multipartFile
     * @param path if old-path not empty, will overwrite file content, if not empty, will create new file and write data
     * @return old-path, if argument old-path is empty, will create a new one.
     * @throws IOException write failed：file path not found or others exceptions
     */
    public String overwrite(File file, String path) throws IOException {
        try (InputStream inputStream = IOUtils.openFileInputStream(file)) {
            storage.overwrite(inputStream, path);
        }
        return path;
    }

    /**
     * 通过相对路径，获取文件输入流
     *
     * @param path 相对路径
     * @return InputStream
     * @throws IOException file not found
     */
    @NotNull
    public InputStream openInputStream(String path) throws IOException {
        return storage.openInputStream(path);
    }

    /**
     * 获取文件实体
     * <p>
     * 这个函数并非任何情况下都有效，只有本机文件和共享文件夹能够做到，
     * 对于FTP、云存储，实现这个函数的时候，需要构造临时文件。
     * <p>
     * {@link File#createTempFile(String, String)}
     *
     * @param path 相对路径
     * @return InputStream 输入流
     * @throws IOException 获取文件输入流失败
     */
    @NotNull
    public File getFile(String path) throws IOException {
        return storage.getFile(path);
    }


    /**
     * 将文件复制到输出流中
     *
     * @param path 相对路径
     * @param os   文件输出流
     * @throws IOException 获取文件输入流失败
     */
    public void copy(String path, OutputStream os) throws IOException {
        storage.copy(path, os);
    }
}
